#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define CAPACITY 4

typedef struct {
	int buf[CAPACITY];
	int in;
	int out;
} buff;

buff buffer1, buffer2;

int buffer_is_empty(buff *buffer)
{
	return buffer->in == buffer->out;
}

int buffer_is_full(buff *buffer)
{
	return (buffer->in + 1) % CAPACITY == buffer->out;
}

int get_item(buff *buffer)
{
	int item;

	item = buffer->buf[buffer->out];
	buffer->out = (buffer->out + 1) % CAPACITY;
	return item;
}

void put_item(int item, buff *buffer)
{
	buffer->buf[buffer->in] = item;
	buffer->in = (buffer->in + 1) % CAPACITY;
}

pthread_mutex_t mutex1, mutex2;
pthread_cond_t wait_empty_buffer1, wait_empty_buffer2;

pthread_cond_t wait_full_buffer1, wait_full_buffer2;

#define ITEM_COUNT (CAPACITY * 2)

void *produce(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		pthread_mutex_lock(&mutex1);
		while (buffer_is_full(&buffer1))
			pthread_cond_wait(&wait_empty_buffer1, &mutex1);
		
		item = 'a' + i;
		put_item(item, &buffer1);
		//printf("produce item: %c\n", item);

		pthread_cond_signal(&wait_full_buffer1);
		pthread_mutex_unlock(&mutex1);
	}
	return NULL;
}

void *compute(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		pthread_mutex_lock(&mutex1);
		while (buffer_is_empty(&buffer1))
			pthread_cond_wait(&wait_full_buffer1, &mutex1);

		item = get_item(&buffer1);
		//printf("	compute got item: %c\n", item);

		pthread_cond_signal(&wait_empty_buffer1);
		pthread_mutex_unlock(&mutex1);

		pthread_mutex_lock(&mutex2);
		while (buffer_is_full(&buffer2))
			pthread_cond_wait(&wait_empty_buffer2, &mutex2);

		item -= 32;
		put_item(item, &buffer2);
		//printf("	compute result item: %c\n", item);
		
		pthread_cond_signal(&wait_full_buffer2);
		pthread_mutex_unlock(&mutex2);
	}
	return NULL;
}

void *consume(void *arg)
{
	int i;
	int item;

	for (i = 0; i < ITEM_COUNT; ++i) {
		pthread_mutex_lock(&mutex2);
		while (buffer_is_empty(&buffer2))
			pthread_cond_wait(&wait_full_buffer2, &mutex2);

		item = get_item(&buffer2);
		//printf("		consume item: %c\n", item);
		printf("%c\n", item);

		pthread_cond_signal(&wait_empty_buffer2);
		pthread_mutex_unlock(&mutex2);
	}
	return NULL;
}

int main()
{
	pthread_t computer_tid, consumer_tid;

	pthread_mutex_init(&mutex1, NULL);
	pthread_mutex_init(&mutex2, NULL);
	pthread_cond_init(&wait_empty_buffer1, NULL);
	pthread_cond_init(&wait_empty_buffer2, NULL);
	pthread_cond_init(&wait_full_buffer1, NULL);
	pthread_cond_init(&wait_full_buffer2, NULL);

	pthread_create(&computer_tid, NULL, compute, NULL);
	pthread_create(&consumer_tid, NULL, consume, NULL);
	produce(NULL);
	pthread_join(computer_tid, NULL);
	pthread_join(consumer_tid, NULL);

	pthread_mutex_destroy(&mutex1);
	pthread_mutex_destroy(&mutex2);
	pthread_cond_destroy(&wait_empty_buffer1);
	pthread_cond_destroy(&wait_empty_buffer2);
	pthread_cond_destroy(&wait_full_buffer1);
	pthread_cond_destroy(&wait_full_buffer2);
	return 0;
}

